
// Copyright (c) 2010-2021 niXman (github dot nixman at pm dot me). All
// rights reserved.
//
// This file is part of YAS(https://github.com/niXman/yas) project.
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//
//
// Boost Software License - Version 1.0 - August 17th, 2003
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef __yas__tests__base__include__yas_object_hpp
#define __yas__tests__base__include__yas_object_hpp

#include <cstdint>
#include <ostream>

#include <yas/object.hpp>
#include "../test.hpp"

/***************************************************************************/

namespace _yas_object_test {

struct type {
    int v;

    type(int v)
        :v(v)
    {}

    template<typename Archive>
    void serialize(Archive &ar) {
        auto o = YAS_OBJECT("type", v);
        ar & o;
    }
};

struct type2 {
    int v0;
    int v1;

    type2(int v0, int v1)
        :v0(v0)
        ,v1(v1)
    {}
};

struct type3 {
    int v0;
    int v1;

    type3(int v0, int v1)
        :v0(v0)
        ,v1(v1)
    {}

    YAS_DEFINE_STRUCT_SERIALIZE("type3", v0, v1);
};

struct type4 {
    int v0;
    int v1;

    type4(int v0, int v1)
        :v0(v0)
        ,v1(v1)
    {}

    YAS_DEFINE_STRUCT_SERIALIZE_NVP("type3", ("vv0", v0), ("vv1", v1));
};

struct type5 {
    int v0;
    int v1;

    type5(int v0, int v1)
        :v0(v0)
        ,v1(v1)
    {}
};
YAS_DEFINE_INTRUSIVE_SERIALIZE("type5", type5, v0, v1);

struct type6 {
    int v0;
    int v1;

    type6(int v0, int v1)
        :v0(v0)
        ,v1(v1)
    {}
};
YAS_DEFINE_INTRUSIVE_SERIALIZE_NVP("type6", type6, ("vv0", v0), ("vv1", v1));

struct type7 {
    int                     v0;
    float                   v1;
    std::string             v2;
    std::vector<uint16_t>   v3;
    
    bool operator==(const type7& other) const
    {
        return v0 == other.v0 && v1 == other.v1 && v2 == other.v2 && v3 == other.v3;
    }
    
    YAS_DEFINE_STRUCT_SERIALIZE("type7", v0, v1, v2, v3);
};

struct bigtype {
    int var1{ 0 };
    unsigned int var2{ 0 };
    std::string var3{};
    std::string var4{};
    unsigned char var5{ 0 };
    unsigned int var6{ 0 };
    unsigned int var7{ 0 };
    unsigned int var8{ 0 };
    unsigned int var9{ 0 };
    unsigned int var10{ 0 };
    unsigned short var11{ 0 };
    unsigned short var12{ 0 };
    unsigned short var13{ 0 };
    unsigned short var14{ 0 };
    float var15{ 0.f };
    float var16{ 0.f };
    float var17{ 0.f };
    float var18{ 0.f };
    int var19{ 0 };
    int var21{ 0 };
    int var22{ 0 };
    unsigned char var23[1728];
    unsigned char var24[50];
    unsigned char var25[80];
    unsigned char var26{ 0 };
    unsigned char var27{ 0 };
    unsigned char var28{ 0 };
    unsigned char var29{ 0 };
    unsigned char var30{ 0 };
    unsigned int var31{ 0 };
    unsigned int var32{ 0 };
    unsigned int var33{ 0 };

    YAS_DEFINE_STRUCT_SERIALIZE_NVP(
        "bigtype"
        ,("var1", var1)
        ,("var2", var2)
        ,("var3", var3)
        ,("var4", var4)
        ,("var5", var5)
        ,("var6", var6)
        ,("var7", var7)
        ,("var8", var8)
        ,("var9", var9)
        ,("var10", var10)
        ,("var11", var11)
        ,("var12", var12)
        ,("var13", var13)
        ,("var14", var14)
        ,("var15", var15)
        ,("var16", var16)
        ,("var17", var17)
        ,("var18", var18)
        ,("var19", var19)
        ,("var21", var21)
        ,("var22", var22)
        ,("var23", var23)
        ,("var24", var24)
        ,("var25", var25)
        ,("var26", var26)
        ,("var27", var27)
        ,("var28", var28)
        ,("var29", var29)
        ,("var30", var30)
        ,("var31", var31)
        ,("var32", var32)
        ,("var33", var33)
    );
};

} // ns _yas_object_test

/***************************************************************************/

template<typename archive_traits>
bool yas_object_test(std::ostream &log, const char *archive_type, const char *test_name) {
    {
        int i0 = 3, i1 = 0;

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);

        auto o0 = YAS_OBJECT_NVP("object0", ("i0", i0));
        oa & o0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);

        auto o1 = YAS_OBJECT_NVP("object0", ("i0", i1));
        ia & o1;

        if ( i0 != i1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        const int i0 = 4;
        int i1 = 0;

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);

        auto o0 = YAS_OBJECT("object1", i0);
        oa & o0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);

        auto o1 = YAS_OBJECT_NVP("object1", ("i0", i1));
        ia & o1;

        if ( i0 != i1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        int i0 = 3, i1 = 0;

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        oa & YAS_OBJECT("object2", i0);

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        ia & YAS_OBJECT_NVP("object2", ("i0", i1));

        if ( i0 != i1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        _yas_object_test::type t0(3), t1(0);

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        oa & t0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        ia & t1;

        if ( t0.v != t1.v ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        _yas_object_test::type2 t0(3, 4), t1(0, 0);

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        auto o0 = YAS_OBJECT_STRUCT("type2", t0, v0, v1);
        oa & o0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        auto o1 = YAS_OBJECT_STRUCT("type2", t1, v0, v1);
        ia & o1;

        if ( t0.v0 != t1.v0 || t0.v1 != t1.v1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        _yas_object_test::type2 t0(3, 4), t1(0, 0);

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        auto o0 = YAS_OBJECT_STRUCT_NVP("type2", t0, ("v0", v0), ("v1", v1));
        oa & o0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        auto o1 = YAS_OBJECT_STRUCT_NVP("type2", t1, ("v0", v0), ("v1", v1));
        ia & o1;

        if ( t0.v0 != t1.v0 || t0.v1 != t1.v1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        _yas_object_test::type3 t0(3, 4), t1(0, 0);

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        oa & t0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        ia & t1;

        if ( t0.v0 != t1.v0 || t0.v1 != t1.v1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        _yas_object_test::type4 t0(3, 4), t1(0, 0);

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        oa & t0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        ia & t1;

        if ( t0.v0 != t1.v0 || t0.v1 != t1.v1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        _yas_object_test::type5 t0(3, 4), t1(0, 0);

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        oa & t0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        ia & t1;

        if ( t0.v0 != t1.v0 || t0.v1 != t1.v1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        _yas_object_test::type6 t0(3, 4), t1(0, 0);

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        oa & t0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        ia & t1;

        if ( t0.v0 != t1.v0 || t0.v1 != t1.v1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        _yas_object_test::bigtype t0, t1;

        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        oa & t0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        ia & t1;

//        if ( t0.v0 != t1.v0 || t0.v1 != t1.v1 ) {
//            YAS_TEST_REPORT(log, archive_type, test_name);
//            return false;
//        }
    }
    {
        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        auto o0 = YAS_OBJECT(nullptr);
        oa & o0;

        typename archive_traits::iarchive ia;
        archive_traits::icreate(ia, oa, archive_type);
        auto o1 = YAS_OBJECT(nullptr);
        ia & o1;
    }
    {
        typename archive_traits::oarchive oa;
        archive_traits::ocreate(oa, archive_type);
        auto o0 = YAS_OBJECT_NVP(nullptr, ("a", 0), ("b", 1));
        oa & o0;

        int a = 0, b = 0;

        bool ex = false;
        __YAS_TRY {
            typename archive_traits::iarchive ia;
            archive_traits::icreate(ia, oa, archive_type);
            auto o1 = YAS_OBJECT(nullptr, a, b);
            ia & o1;
        } __YAS_CATCH (const yas::serialization_exception &) {
            ex = true;
        }
        if ( ex ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
        if ( a != 0 || b != 1 ) {
            YAS_TEST_REPORT(log, archive_type, test_name);
            return false;
        }
    }
    {
        if ( archive_traits::iarchive_type::flags() & yas::json ) {
            if ( !(archive_traits::iarchive_type::flags() & yas::compacted)) {
                typename archive_traits::oarchive oa;
                archive_traits::ocreate(oa, archive_type);
                auto o0 = YAS_OBJECT_NVP(nullptr, ("b", 1), ("c", 2), ("a", 0));
                oa & o0;

                int a = 0, b = 0;

                bool ex = false;
                __YAS_TRY {
                    typename archive_traits::iarchive ia;
                    archive_traits::icreate(ia, oa, archive_type);
                    auto o1 = YAS_OBJECT(nullptr, b, a);
                    ia & o1;
                } __YAS_CATCH (const yas::serialization_exception &) {
                    ex = true;
                }
                if ( ex ) {
                    YAS_TEST_REPORT(log, archive_type, test_name);
                    return false;
                }
                if ( a != 0 || b != 1 ) {
                    YAS_TEST_REPORT(log, archive_type, test_name);
                    return false;
                }
            }
        }
    }
    {
        if ( archive_traits::iarchive_type::flags() & yas::json ) {
            if ( !(archive_traits::iarchive_type::flags() & yas::compacted)) {
                typename archive_traits::oarchive oa;
                archive_traits::ocreate(oa, archive_type);
                auto o0 = YAS_OBJECT_NVP(nullptr, ("b", 1), ("c", 2), ("a", 0));
                oa & o0;

                int c = 0;

                bool ex = false;
                __YAS_TRY {
                    typename archive_traits::iarchive ia;
                    archive_traits::icreate(ia, oa, archive_type);
                    auto o1 = YAS_OBJECT(nullptr, c);
                    ia & o1;
                } __YAS_CATCH (const yas::serialization_exception &) {
                    ex = true;
                }
                if ( ex ) {
                    YAS_TEST_REPORT(log, archive_type, test_name);
                    return false;
                }
                if ( c != 2 ) {
                    YAS_TEST_REPORT(log, archive_type, test_name);
                    return false;
                }
            }
        }
    }

#if 0
    {
        _yas_object_test::type7 obj1;
        obj1.v0 = -4;
        obj1.v1 = 3.141592;
        obj1.v2 = "hello world";
        obj1.v3 = {0,1,2,3,4,5,6,7,8,9};
        
        {
            const size_t expected_size = yas::saved_size<yas::binary>(obj1);
            std::vector<char> buf;
            buf.reserve(expected_size);
            yas::save<yas::binary|yas::mem>(yas::vector_ostream<char>(buf), obj1);

            if (expected_size != buf.size() || buf.capacity() != buf.size())
            {
                YAS_TEST_REPORT(log, archive_type, test_name);
                return false;
            }

            _yas_object_test::type7 obj2;
            yas::load<yas::binary|yas::mem>(buf, obj2);

            if (!(obj1 == obj2))
            {
                YAS_TEST_REPORT(log, archive_type, test_name);
                return false;
            }
        }
        
        {
            const size_t expected_size = yas::saved_size<yas::text>(obj1);
            std::vector<char> buf;
            buf.reserve(expected_size);
            yas::save<yas::text|yas::mem>(yas::vector_ostream<char>(buf), obj1);

            if (expected_size != buf.size() || buf.capacity() != buf.size())
            {
                YAS_TEST_REPORT(log, archive_type, test_name);
                return false;
            }

            _yas_object_test::type7 obj2;
            yas::load<yas::text|yas::mem>(buf, obj2);

            if (!(obj1 == obj2))
            {
                YAS_TEST_REPORT(log, archive_type, test_name);
                return false;
            }
        }
        
        {
            const size_t expected_size = yas::saved_size<yas::json>(obj1);
            std::vector<char> buf;
            buf.reserve(expected_size);
            yas::save<yas::json|yas::mem>(yas::vector_ostream<char>(buf), obj1);

            if (expected_size != buf.size() || buf.capacity() != buf.size())
            {
                YAS_TEST_REPORT(log, archive_type, test_name);
                return false;
            }

            _yas_object_test::type7 obj2;
            yas::load<yas::json|yas::mem>(buf, obj2);

            if (!(obj1 == obj2))
            {
                YAS_TEST_REPORT(log, archive_type, test_name);
                return false;
            }
        }
    }
#endif
    return true;
}

/***************************************************************************/

#endif // __yas__tests__base__include__yas_object_hpp
